#include "Texture.hpp"
#include <locale>
#include <codecvt>
#include <map>
#include <vector>

static bool ILinitialized = false;
static std::map<std::string, gl::Texture*> textureList;

static std::vector<int> textureUnits;
static GLuint textureCounter = 0;

static int generateFreeTextureUnit()
{
	if(textureUnits.size() > 0)
	{
		int textureUnit = textureUnits.back();
		textureUnits.pop_back();
		return textureUnit;
	}
	return textureCounter++;
}

static int powerOfTwo(int num)
{
	if(num != 0)
	{
		num--;
		num |= (num >> 1);
		num |= (num >> 2);
		num |= (num >> 4);
		num |= (num >> 8);
		num |= (num >> 16);
		num++;
	}
	return num;
}

namespace gl
{

Texture::Texture(GLuint tex, int texUnit, int width, int height) :
	mTex(tex),
	mTexUnit(texUnit),
	mWidth(width),
	mHeight(height)
{
	
}

gl::Texture* Texture::loadFromFile(const std::string &filepath)
{
	std::string newFilepath = phx::getCurrentDir() + "\\" + filepath;
	//size_t extension = newFilepath.find_last_of('.');
	//if(extension == std::string::npos)
	//	newFilepath += ".tga";
	gl::Texture *cachedTexture = getFromPath(newFilepath);
	if(cachedTexture)
		return cachedTexture;
	std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
	std::wstring wide = converter.from_bytes(newFilepath.c_str());

	if(!ILinitialized)
	{
		ilInit();
		iluInit();
		ILinitialized = true;
	}

	ILuint imageId = 0;
	ilGenImages(1, &imageId);
	ilBindImage(imageId);

	ilEnable(IL_ORIGIN_SET);
	ilOriginFunc(IL_ORIGIN_LOWER_LEFT);
	if(!ilLoadImage(wide.c_str()))
	{
		printILerror();
		ilDeleteImages(1, &imageId);
		ilBindImage(NULL);
		std::wcerr << "Failed to load texture: " << wide << std::endl;
		return nullptr;
	}
	//std::wcerr << "Successfully loaded texture: " << wide << std::endl;
	ilConvertImage(IL_RGBA, IL_UNSIGNED_BYTE);

	int imgWidth = ilGetInteger(IL_IMAGE_WIDTH);
	int imgHeight = ilGetInteger(IL_IMAGE_HEIGHT);
	int texWidth = powerOfTwo(imgWidth);
	int texHeight = powerOfTwo(imgHeight);
	if(imgWidth != texWidth || imgHeight != texHeight)
	{
		iluImageParameter(ILU_PLACEMENT, ILU_UPPER_LEFT);
		iluEnlargeCanvas(texWidth, texHeight, 1);
		imgWidth = texWidth;
		imgHeight = texHeight;
	}

	GLuint tex = 0;
	//int texUnit = generateFreeTextureUnit();
	int texUnit = 0;
	glActiveTexture(GL_TEXTURE0 + texUnit);
	std::wcout << "Using texture unit " << texUnit << " for " << wide << std::endl;
	glGenTextures(1, &tex);

	glBindTexture(GL_TEXTURE_2D, tex);
	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	//GLfloat max_aniso = 0.0f;
	//glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_aniso);
	//glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_aniso);

	gl::Texture *texture = new gl::Texture(tex, texUnit, imgWidth, imgHeight);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture->mWidth, texture->mHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, ilGetData());
	glGenerateMipmap(GL_TEXTURE_2D);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	ilDeleteImages(1, &imageId);
	ilBindImage(NULL);
	glBindTexture(GL_TEXTURE_2D, NULL);

	textureList[newFilepath] = texture;

	return texture;
}

void Texture::use()
{
	glBindTexture(GL_TEXTURE_2D, mTex);
}

int Texture::getUnit()
{
	return mTexUnit;
}

gl::Texture* Texture::getFromPath(const std::string &filepath)
{
	std::map<std::string, gl::Texture*>::iterator it = textureList.find(filepath);
	if(it != textureList.end())
		return it->second;
	return nullptr;
}

Texture::~Texture()
{
	glDeleteTextures(1, &mTex);
	// This texture unit is now free to be used.
	textureUnits.push_back(mTexUnit);
}

}